-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local lu = require('luaunit')

-- require所有依赖
local log = require 'mc.logging'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local c_factory = require 'mc.orm.factory'
local sd_bus = require 'sd_bus.org_freedesktop_dbus'
local c_network_adapter = require 'device.class.network_adapter'
local comm_fun = require 'device.class.nic_mgmt.comm_fun'
local comm_defs = require 'device.class.nic_mgmt.comm_defs'
local card_init = require 'device.class.nic_mgmt.card.card_init'

local card_mgmt = require 'device.class.nic_mgmt.card_mgmt'

-- 记录原始实现
local origs = {}
local function save_orig(tbl, key)
    origs[tbl] = origs[tbl] or {}
    origs[tbl][key] = tbl[key]
end
local function restore_all()
    for tbl, keys in pairs(origs) do
        for k, v in pairs(keys) do tbl[k] = v end
    end
end

TestCardMgmt = {}

function TestCardMgmt:setUp()
    -- mock log
    save_orig(log, 'notice')
    save_orig(log, 'error')
    log.notice = function() end
    log.error = function() end
    -- mock comm_fun
    save_orig(comm_fun, 'set_interface_add_signal')
    save_orig(comm_fun, 'get_object_name_by_device_path')
    save_orig(comm_fun, 'get_all_device_paths')
    comm_fun.set_interface_add_signal = function(bus, sig_slot, pattern, iface, cb)
        TestCardMgmt._called_set_interface_add_signal = { bus, sig_slot, pattern, iface, cb }
        cb('/mock/path')
    end
    comm_fun.get_object_name_by_device_path = function() return 'obj_name' end
    comm_fun.get_all_device_paths = function() return { '/mock/path' } end
    -- mock comm_defs
    save_orig(comm_defs, 'CARD_DEVICE_PATH_PATTERN')
    save_orig(comm_defs, 'NETWORK_ADAPTER_DEVICE_INTERFACE')
    save_orig(comm_defs, 'MACA_SERVICE')
    save_orig(comm_defs, 'MDB_PATH')
    save_orig(comm_defs, 'MDB_INTERFACE')
    save_orig(comm_defs, 'PCIE_DEVICE_INTERFACE')
    comm_defs.CARD_DEVICE_PATH_PATTERN = '/mock/pattern'
    comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE = 'iface'
    comm_defs.MACA_SERVICE = 'svc'
    comm_defs.MDB_PATH = '/mdb'
    comm_defs.MDB_INTERFACE = 'mdb_iface'
    comm_defs.PCIE_DEVICE_INTERFACE = 'iface1'
    -- mock ctx
    save_orig(ctx, 'get_context_or_default')
    ctx.get_context_or_default = function() return {} end
    -- mock mdb
    save_orig(mdb, 'register_interface')
    save_orig(mdb, 'get_object')
    mdb.register_interface = function() end
    mdb.get_object = function()
        return {
            get_property = function(self, prop)
                if prop == 'DeviceName' then return 0, 'NodeId' end
                return 0, 'val'
            end
        }
    end
    -- mock c_network_adapter
    save_orig(c_network_adapter, 'create_mdb_object')
    save_orig(c_network_adapter, 'collection')
    c_network_adapter.create_mdb_object = function(tbl) return tbl end
    c_network_adapter.collection = {
        find = function(self, q)
            return { children = {}, NetworkPortCount = 1, NodeId = q and q.ID or 'NodeId' }
        end
    }
    -- mock c_factory
    save_orig(c_factory, 'get_instance')
    c_factory.get_instance = function() return { create_object = function() end } end
    -- mock card_init
    save_orig(card_init, 'init')
    TestCardMgmt._card_init_called = {}
    card_init.init = function(obj) TestCardMgmt._card_init_called.obj = obj end
    -- mock sd_bus
    save_orig(sd_bus, 'ObjMgrPropertiesChanged')
    save_orig(sd_bus, 'MatchRule')
    sd_bus.ObjMgrPropertiesChanged = { name = 'n', interface = 'i' }
    sd_bus.MatchRule = { signal = function() return { with_path = function() return 'sig' end } end }
end

function TestCardMgmt:tearDown()
    restore_all()
end

function TestCardMgmt:test_ctor_and_init()
    local bus = {}
    local mgr = card_mgmt
    mgr.bus = bus
    mgr.objects = {}
    mgr.sig_slot = {}
    lu.assertIs(mgr.bus, bus)
    lu.assertIsTable(mgr.objects)
    lu.assertIsTable(mgr.sig_slot)
end

function TestCardMgmt:test_init_obj_and_register_children()
    local bus = {}
    local mgr = card_mgmt
    mgr.bus = bus
    mgr.objects = {}
    mgr.sig_slot = {}
    -- mock bus:call，确保get_node_id_by_device_path能正常返回
    bus.call = function(self, service, path, interface, method, signature, context, device_path, interfaces)
        return { ['svc'] = { 'iface1' } }
    end
    -- 执行
    mgr:init_obj('/mock/path')
    lu.assertIsTable(mgr.objects['/mock/path'])
    local card_orm_obj = mgr.objects['/mock/path']
    local port_orm_obj = { id = 1 }
    mgr:register_children(port_orm_obj, card_orm_obj)
    lu.assertEquals(card_orm_obj.children[1], port_orm_obj)
    lu.assertIs(TestCardMgmt._card_init_called.obj, card_orm_obj)
end

function TestCardMgmt:test_get_orm_obj_by_device_path()
    local bus = {}
    local mgr = card_mgmt
    mgr.bus = bus
    mgr.objects = { ['/mock/path'] = { id = 1 } }
    local obj = mgr:get_orm_obj_by_device_path('/mock/path')
    lu.assertEquals(obj.id, 1)
    local obj2 = mgr:get_orm_obj_by_device_path('/notfound')
    lu.assertNil(obj2)
end

function TestCardMgmt:test_init()
    local bus = {}
    local mgr = card_mgmt
    mgr.bus = bus
    mgr.objects = {}
    mgr.sig_slot = {}
    -- mock bus:call，确保get_node_id_by_device_path能正常返回
    bus.call = function(self, service, path, interface, method, signature, context, device_path, interfaces)
        return { ['svc'] = { 'iface1' } }
    end
    -- 执行
    mgr:init()
    lu.assertIsTable(mgr.objects['/mock/path'])
end

function TestCardMgmt:test_create_card_orm_object_nodeid_nil()
    local orig_get_object = mdb.get_object
    mdb.get_object = function() return { get_property = function() return 1, nil end } end
    local mgr = card_mgmt
    mgr.bus = {}
    mgr.objects = {}
    mgr.sig_slot = {}
    mgr:init_obj('/mock/path')
    lu.assertNil(mgr.objects['/mock/path'])
    mdb.get_object = orig_get_object
end

function TestCardMgmt:test_create_card_orm_object_objectname_nil()
    local orig_get_object_name = comm_fun.get_object_name_by_device_path
    comm_fun.get_object_name_by_device_path = function() return nil end
    local mgr = card_mgmt
    mgr.bus = {}
    mgr.objects = {}
    mgr.sig_slot = {}
    mgr:init_obj('/mock/path')
    lu.assertNil(mgr.objects['/mock/path'])
    comm_fun.get_object_name_by_device_path = orig_get_object_name
end

function TestCardMgmt:test_init_obj_already_exists()
    local bus = {}
    local mgr = card_mgmt
    mgr.bus = bus
    mgr.objects = { ['/mock/path'] = { id = 1 } }
    mgr.sig_slot = {}
    mgr:init_obj('/mock/path')
    lu.assertEquals(mgr.objects['/mock/path'].id, 1)
end

function TestCardMgmt:test_get_orm_obj_by_device_path_log_error()
    local log_err_called = false
    local orig_error = log.error
    log.error = function() log_err_called = true end
    local bus = {}
    local mgr = card_mgmt
    mgr.bus = bus
    mgr.objects = {}
    local obj = mgr:get_orm_obj_by_device_path('/notfound')
    lu.assertNil(obj)
    lu.assertTrue(log_err_called)
    log.error = orig_error
end

function TestCardMgmt:test_firmware_version_sync_to_ports()
    local port1 = { FirmwareVersion = "old_version_1" }
    local port2 = { FirmwareVersion = "old_version_2" }
    local orm_obj = {
        FirmwareVersion = "old_version", 
        children = { port1, port2 }
    }
    
    local captured_callback = nil
    local mock_bus = {
        match = function(self, signal, callback)
            captured_callback = callback
            return {}
        end,
        call = function(self, service, path, interface, method, signature, context, device_path, interfaces)
            return { ['svc'] = { 'iface1' } }
        end
    }
    
    local original_create_card_orm_object = c_network_adapter.collection.find
    c_network_adapter.collection.find = function(self, q)
        return orm_obj
    end
    
    local mgr = card_mgmt
    mgr.bus = mock_bus
    mgr.objects = {}
    mgr.sig_slot = {}
    
    local mock_msg = {
        read = function(self, pattern)
            local interface = comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE
            local props = {
                FirmwareVersion = {
                    value = function() return "new_version_1.2.3" end
                }
            }
            return interface, props
        end
    }
    
    mgr:init_obj('/mock/path')
    
    lu.assertNotNil(captured_callback, "Should capture property change callback")
    
    captured_callback(mock_msg)
    
    lu.assertEquals(orm_obj.FirmwareVersion, "new_version_1.2.3")
    lu.assertEquals(port1.FirmwareVersion, "new_version_1.2.3") 
    lu.assertEquals(port2.FirmwareVersion, "new_version_1.2.3")
    
    c_network_adapter.collection.find = original_create_card_orm_object
end